<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS-Interpreter Backwards Stepping Demo</title>
  <link href="style.css" rel="stylesheet" type="text/css">
  <script src="../acorn.js"></script>
  <script src="../interpreter.js"></script>
  <script src="serialize.js"></script>
  <script src="diff_match_patch.js"></script>
  <script>
    var myInterpreter;
    var serializationStack = [];
    var dmp;
    if (typeof diff_match_patch === 'function') {
      dmp = new diff_match_patch();
      console.log('Using DMP for compression.');
    } else {
      console.log('DMP not available, each step will cost ~300 kb.');
    }

    function initDialogs(interpreter, globalObject) {
      var wrapper = function alert(text) {
        return window.alert(arguments.length ? text : '');
      };
      interpreter.setProperty(globalObject, 'alert',
          interpreter.createNativeFunction(wrapper));

      wrapper = function prompt(text, defaultValue) {
        return window.prompt(arguments.length > 0 ? text : '',
                             arguments.length > 1 ? defaultValue : '');
      };
      interpreter.setProperty(globalObject, 'prompt',
          interpreter.createNativeFunction(wrapper));
    }

    function parseButton() {
      serializationStack.length = 0;
      var code = document.getElementById('code').value;
      myInterpreter = new Interpreter(code, initDialogs);
      disable('');
      document.getElementById('stepBackwardsButton').disabled = 'disabled';
    }

    function stepBackwardsButton() {
      var json = serializationStack.pop();
      if (!serializationStack.length) {
        document.getElementById('stepBackwardsButton').disabled = 'disabled';
      }
      if (!json) {
        return;
      }
      if (dmp && serializationStack.length) {
        // Uncompress the next previous serialization.
        var delta = serializationStack.pop();
        var previousDiff = dmp.diff_fromDelta(json, delta);
        serializationStack.push(dmp.diff_text2(previousDiff));
      }
      json = JSON.parse(json);
      // Create a clean interpreter with the same initialization functions.
      myInterpreter = new Interpreter('', initDialogs);
      deserialize(json, myInterpreter);
      updateHighlight();
    }

    function stepForwardsButton() {
      var json = JSON.stringify(serialize(myInterpreter));
      if (dmp && serializationStack.length) {
        // Compress the previous serialization as a delta of the current one.
        var previousJson = serializationStack.pop();
        var diffs = dmp.diff_main(json, previousJson);
        dmp.diff_cleanupEfficiency(diffs);
        serializationStack.push(dmp.diff_toDelta(diffs));
      }
      serializationStack.push(json);
      document.getElementById('stepBackwardsButton').disabled = '';
      updateHighlight();
      try {
        var ok = myInterpreter.step();
      } finally {
        if (!ok) {
          disable('disabled');
        }
      }
    }

    function updateHighlight() {
      var stack = myInterpreter.getStateStack();
      if (stack.length) {
        var node = stack[stack.length - 1].node;
        var start = node.start;
        var end = node.end;
      } else {
        var start = 0;
        var end = 0;
      }
      createSelection(start, end);
    }

    function runButton() {
      disable('disabled');
      if (myInterpreter.run()) {
        // Async function hit.  There's more code to run.
        setTimeout(runButton, 100);
      }
    }

    function disable(disabled) {
      document.getElementById('stepForwardsButton').disabled = disabled;
      document.getElementById('stepBackwardsButton').disabled = disabled;
      document.getElementById('runButton').disabled = disabled;
    }

    function createSelection(start, end) {
      var field = document.getElementById('code');
      if (field.createTextRange) {
        var selRange = field.createTextRange();
        selRange.collapse(true);
        selRange.moveStart('character', start);
        selRange.moveEnd('character', end);
        selRange.select();
      } else if (field.setSelectionRange) {
        field.setSelectionRange(start, end);
      } else if (field.selectionStart) {
        field.selectionStart = start;
        field.selectionEnd = end;
      }
      field.focus();
    }
  </script>
</head>
<body>
  <h1>JS-Interpreter Backwards Stepping Demo</h1>

  <p>The intepreter may be stepped forwards like a debugger.  Additionally, by
  saving a stack of serializations, the interpreter may also be stepped backwards.</p>

  <p>Since each serialization consumes about 300 kb of memory, this demo uses
  <a href="https://github.com/google/diff-match-patch">Diff Match Patch</a>
  to only store the deltas between each step.</p>

  <p>Click <em>Parse</em>, then click <em>Step &larr;</em> and/or <em>Step &rarr;</em>
  repeatedly.  Open your browser's console for errors.</p>

  <p><textarea id="code" style="height: 10em;" spellcheck="false">
var a = prompt('First Number', '1');
var b = prompt('Second Number', '2');
var sum = Number(a) + Number(b);
alert('Sum: ' + sum);
</textarea><br>
  <button onclick="parseButton()">Parse</button>
  <button onclick="stepBackwardsButton()" id="stepBackwardsButton" disabled="disabled">Step &larr;</button>
  <button onclick="stepForwardsButton()" id="stepForwardsButton" disabled="disabled">Step &rarr;</button>
  <button onclick="runButton()" id="runButton" disabled="disabled">Run</button>
  </p>

  <p>Back to the <a href="../docs.html">JS-Interpreter documentation</a>.</p>

  <script>
    disable('disabled');
  </script>
</body>
</html>
